﻿using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Service.Core.Repository.Config;
using Service.Core.Repository.Core;
using Service.Core.Repository.Event;
using Service.Core.Repository.UnitOfWorks;
using Service.Framework;
using Service.Framework.ApplicationEventBus;
using Service.Framework.AppStartup;
using Service.Framework.OperationLog;
using Service.Framework.SmsRecordLog;
using Service.Framework.Utils;
using Service.Framework.Utils.Helper;
using Service.Repository;
using SqlSugar;
using System.Reflection;
using System.Security.Claims;
using System.Text;

namespace Service.Core.Repository;

/// <summary>
/// 
/// </summary>
public class CoreRepositoryStartup : StartupModule<CoreRepositoryStartup>
{
    public override int Order { get; set; } = 40;


    /// <summary>
    /// 
    /// </summary>
    /// <param name="webApplicationBuilder"></param>
    public override void ConfigureServices(WebApplicationBuilder webApplicationBuilder)
    {


        AppSettings<DbConfig>? appSettings =
            webApplicationBuilder.Services.BuildServiceProvider().GetService<AppSettings<DbConfig>>();
        var services = webApplicationBuilder.Services;
        List<ConnectionConfig> connectionConfigs = new List<ConnectionConfig>()
        {
            new ConnectionConfig(){
                ConfigId = DbTenantEnum.Default.ToString(),
                ConnectionString = appSettings?.Instance.Master.ConnectionString,
                DbType = StringToDbTypeEnum(appSettings?.Instance.Master.DbType),
                InitKeyType = InitKeyType.Attribute,
                IsAutoCloseConnection = true
            }
        };

        if (appSettings?.Instance.LogDb != null)
        {
            connectionConfigs.Add(new ConnectionConfig()
            {
                ConfigId = DbTenantEnum.Log.ToString(),
                ConnectionString = appSettings?.Instance.LogDb.ConnectionString,
                DbType = StringToDbTypeEnum(appSettings?.Instance.LogDb.DbType),
                IsAutoCloseConnection = true,
                InitKeyType = InitKeyType.Attribute,
            });
        }
        if (appSettings?.Instance?.DataSources != null && appSettings.Instance.DataSources.Any())
        {
            foreach (var dataSource in appSettings.Instance.DataSources)
            {
                connectionConfigs.Add(new ConnectionConfig()
                {
                    ConfigId = dataSource.ConfigId,
                    ConnectionString = dataSource.ConnectionString,
                    DbType = StringToDbTypeEnum(dataSource.DbType),
                    IsAutoCloseConnection = true,
                    InitKeyType = InitKeyType.Attribute,
                });
            }
        }


        StaticConfig.CompleteInsertableFunc =
        StaticConfig.CompleteUpdateableFunc =
        StaticConfig.CompleteDeleteableFunc = it =>
        {
            //反射的方法可能多个就需要用GetMethods().Where
            var method = it.GetType().GetMethod("EnableDiffLogEvent");
            method.Invoke(it, new object[] { null });
        };

        services.AddScoped<ISqlSugarClient>(seriverProvider =>
       {
           ILogger<DbContext>? log = seriverProvider.GetService<ILogger<DbContext>>();
           DbContext dbContext = new DbContext(connectionConfigs, dbs =>
           {
        
               foreach (var connection in connectionConfigs)
               {
                   var db = dbs.GetConnection(connection.ConfigId);
                   db.Aop.OnLogExecuting = (sql, pars) =>
                   {


                       log.LogInformation("========================sql语句====================================");
                       log.LogInformation(UtilMethods.GetNativeSql(sql, pars));


                       //监控sql执行实际超过5秒的
                       if (db.Ado.SqlExecutionTime.TotalSeconds > 5)
                       {
                           //代码CS文件名
                           var fileName = db.Ado.SqlStackTrace.FirstFileName;
                           //代码行数
                           var fileLine = db.Ado.SqlStackTrace.FirstLine;
                           //方法名
                           var firstMethodName = db.Ado.SqlStackTrace.FirstMethodName;

                           log.LogWarning($"sql 文件名：{fileName}，代码行数：{fileLine}，方法名：{firstMethodName}");
                           //db.Ado.SqlStackTrace.MyStackTraceList[1].xxx 获取上层方法的信息 
                           //to do 记录日志，发送邮件提醒开发人员
                       }
                   };
                   //差异日志功能
                   db.Aop.OnDiffLogEvent = it =>
                   {
                       //操作前记录  包含： 字段描述 列名 值 表名 表描述
                       var editBeforeData = it.BeforeData; //插入Before为null，之前还没进库
                       var editAfterData = it.AfterData; //操作后记录   包含： 字段描述 列名 值  表名 表描述
                       var sql = it.Sql;
                       var parameter = it.Parameters;
                       var data = it.BusinessData; //这边会显示你传进来的对象
                       var time = it.Time;
                       var diffType = it.DiffType; //enum insert 、update and delete  
                       if (DiffType.update.Equals(diffType))
                       {
                           log.LogInformation($"差异日志：操作类型：{diffType}，操作时间：{time}，差异字段：{JsonSerializeUtil.SerializeObject(getDiff(editBeforeData, editAfterData))}");
                       }



                       //做数据朔源，暂时不考虑
                   };
                   db.Aop.DataExecuting = (oldValue, entityInfo) =>
                   {
                       var softIsDelete = (Boolean?)entityInfo.EntityValue.GetType()
                           .GetProperty(nameof(IEntity<long>.IsDelete))
                           ?.GetValue(entityInfo.EntityValue);

                       var httpContextAccessor = services.BuildServiceProvider().GetService<IHttpContextAccessor>();

                       if (httpContextAccessor != null)
                       {
                           var userId = httpContextAccessor.HttpContext?.User?.FindFirst("sub")?.Value;
                           long? id = string.IsNullOrWhiteSpace(userId)
                               ? null
                               : long.Parse(userId);

                           var tenantId = httpContextAccessor.HttpContext?.User?.FindFirst("tenant_id")?.Value;
                           tenantId = string.IsNullOrWhiteSpace(tenantId)
                               ? DbTenantEnum.Default.ToString()
                               : tenantId;


                           if (entityInfo.PropertyName == nameof(IEntityTenant.TenantId) &&
                               entityInfo.OperationType == DataFilterType.InsertByObject &&
                               (oldValue == null || string.IsNullOrEmpty(Convert.ToString(oldValue))))
                               entityInfo.SetValue(tenantId);
                           if (entityInfo.PropertyName == nameof(IEntity<long>.Id) &&
                               entityInfo.OperationType == DataFilterType.InsertByObject)
                           {
                               if (oldValue == null || (oldValue is long &&
                                                        Convert.ToInt64(oldValue) == default))
                                   entityInfo.SetValue(SnowflakeUtil.Instance().GetId());
                           }

                           if (entityInfo is { PropertyName: nameof(IEntity<long>.CreateById), OperationType: DataFilterType.InsertByObject })
                               entityInfo.SetValue(id);
                           if (entityInfo is { PropertyName: nameof(IEntity<long>.UpdateById), OperationType: DataFilterType.UpdateByObject })
                               entityInfo.SetValue(id);


                           if (entityInfo is { PropertyName: nameof(IEntity<long>.DeleteById), OperationType: DataFilterType.UpdateByObject } &&
                               softIsDelete.HasValue && softIsDelete.Value)
                               entityInfo.SetValue(id);

                           if (entityInfo is { PropertyName: nameof(IEntity<long>.DeleteTime), OperationType: DataFilterType.UpdateByObject } &&
                               softIsDelete.HasValue && softIsDelete.Value)
                               entityInfo.SetValue(DateTime.Now);
                       }

                       if (entityInfo is { PropertyName: nameof(IEntity<long>.CreateTime), OperationType: DataFilterType.InsertByObject })
                           entityInfo.SetValue(DateTime.Now);
                       if (entityInfo is { PropertyName: nameof(IEntity<long>.UpdateTime), OperationType: DataFilterType.UpdateByObject })
                           entityInfo.SetValue(DateTime.Now);
                   };
               }
           });
           return dbContext;
       });
        services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
        services.AddTransient(typeof(IRepository<,>), typeof(Repository<,>));
        services.AddScoped<IUnitOfWorkManage, UnitOfWorkManage>();
        services.AddScoped(typeof(IApplicationEventHandler<OperationLogAges>), typeof(OperationLogHandle));
        services.AddScoped(typeof(IApplicationEventHandler<SmsRecordLogAges>), typeof(SmsRecordLogHandle));
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="webApplication"></param>
    public override void Configure(WebApplication webApplication)
    {
        // Method intentionally left empty.
    }


    private static DbType StringToDbTypeEnum(string dataBaseType)
    {
        DbType dbType = dataBaseType switch
        {
            "mysql" => DbType.MySql,
            "oracle" => DbType.Oracle,
            "mssql" => DbType.SqlServer,
            _ => DbType.MySql
        };

        return dbType;
    }



    /// <summary>
    /// 比较两个数据对象的修改内容
    /// </summary>
    /// <param name="beforeData"></param>
    /// <param name="afterData"></param>
    /// <returns></returns>
    public static diffLog getDiff(List<DiffLogTableInfo> beforeData, List<DiffLogTableInfo> afterData)
    {
        string tableName = string.Empty;
        string mianID = null;
        if (beforeData != null)
        {
            var keyCoulumn = beforeData[0].Columns.FirstOrDefault(p => p.IsPrimaryKey == true);
            if (keyCoulumn != null)
            {
                mianID = keyCoulumn.Value.ToString();
            }
            tableName = beforeData[0].TableName;
        }
        else if (afterData != null)
        {
            var keyCoulumn = afterData[0].Columns.FirstOrDefault(p => p.IsPrimaryKey == true);
            if (keyCoulumn != null)
            {
                mianID = keyCoulumn.Value.ToString();
            }
            tableName = afterData[0].TableName;
        }

        StringBuilder sb = new StringBuilder();
        if (beforeData != null && afterData != null)
        {
            var befroeColumns = beforeData[0].Columns;
            var afterCloums = afterData[0].Columns;
            foreach (var item in befroeColumns)
            {
                if (IgnoreColumns.Contains(item.ColumnName.ToLower()))
                    continue;
                var afterItem = afterCloums.FirstOrDefault(p => p.ColumnName == item.ColumnName && !p.Value.Equals(item.Value));
                if (afterItem != null)
                {
                    sb.Append($"[字段:{item.ColumnName},修改前:{item.Value}，修改后:{afterItem.Value}]");
                }
            }
        }

        return new diffLog { ID = mianID, diffData = sb.ToString(), TableName = tableName };
    }



    /// <summary>
    /// 忽略的字段
    /// </summary>
    public static readonly List<string> IgnoreColumns = new List<string>()
        {
            "create_time",
            "update_time",
            "delete_time",
            "is_delete",
            "create_id",
            "delete_id",
            "update_id",
        };


    public class diffLog
    {
        public string ID { get; init; }

        public string TableName { get; init; }

        public string diffData { get; init; }
    }
}